Skip to content

Conversation

@rekmarks
Copy link
Member

@rekmarks rekmarks commented Jan 14, 2026

Adds an actual caplet implementation, the echo caplet, and enables installing it and retrieving a reference to its root object from the extension's background console. The returned object is not actually a usable presence, but we implement this in #754.

In detail:

  • Adds echo caplet
  • Store and retrieve caplet root krefs from the kernel API
  • Add caplet manifest loading and installation
  • Return subcluster id, root kref, and bootstrap result from launchSubcluster()
  • Add omnium.caplet.load() for dynamic caplet loading

Part 3 of 4 in PR stack
Depends on: #752
Followed by: #754


Note

Introduces caplet plumbing and updates kernel launch semantics to return identifiers needed to reference caplet roots.

  • Kernel API: launchSubcluster() now returns SubclusterLaunchResult { subclusterId, bootstrapRootKref, bootstrapResult }; SubclusterManager and Kernel updated; types exported
  • Kernel browser facade/CapTP: facade launchSubcluster now returns { subclusterId, rootKref }; added getVatRoot(krefString); CapTP/RPC handler/spec updated to return structured result and normalize bootstrapResult undefined→null
  • Omnium (extension): added Echo caplet (bundle + manifest) and static copy in build; background exposes omnium.caplet.load(); CapletController now stores rootKref, supports getCapletRoot(), and uses kernel facade launchSubcluster/getVatRoot; integration tests added
  • Tests/fixtures: updated across kernel, browser-runtime, node helpers, and persistence/tests to consume new launch result and root krefs
  • Tooling: ESLint allows caplet JS under src/**/caplets; build script bundles caplets

Written by Cursor Bugbot for commit bc6e395. This will update automatically on new commits. Configure here.

@rekmarks rekmarks requested a review from a team as a code owner January 14, 2026 22:28
@rekmarks rekmarks marked this pull request as draft January 14, 2026 23:26
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 451a6dd to 605f8d9 Compare January 14, 2026 23:45
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch 2 times, most recently from 6736065 to 89e5113 Compare January 15, 2026 00:00
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 605f8d9 to f08383c Compare January 15, 2026 00:01
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from f08383c to 34a1f08 Compare January 15, 2026 06:25
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch from 89e5113 to ee93a06 Compare January 15, 2026 06:31
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 34a1f08 to 4caf528 Compare January 15, 2026 21:29
github-merge-queue bot pushed a commit that referenced this pull request Jan 20, 2026
Answers @kumavis's challenge of "What're you afraid of CapTP or
something?" by replacing our kernel JSON-RPC API with `E()` on a facet
of the kernel. This makes it easy to expose as much of the kernel API as
we want via eventual send, and allows us to benefit from pipelining
internally. In addition, it facilitates the removal of the command
stream and the related RPC API logic. Finally, a number of
rationalizations are applied to the extension and omnium.

This PR is part of a stack followed by: #752, #753, and #754



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces CapTP-based communication and removes the JSON-RPC command
stream.
> 
> - Add CapTP utilities in `kernel-browser-runtime`:
`makeBackgroundCapTP`, `makeKernelCapTP`, `makeKernelFacade`, and
JSON-RPC `captp` notification helpers; export new `KernelFacade` and
`CapTPMessage` types
> - Update `kernel-worker` to use CapTP for background↔kernel messaging;
initialize kernel without a command stream; route internal RPC only for
panel/internal comms
> - Refactor `@MetaMask/ocap-kernel` to drop command stream handling and
kernel RPC entrypoints; `Kernel.make` now accepts only platform services
and the database; adjust `stop()` accordingly
> - Migrate extension and omnium backgrounds/offscreens to CapTP over
`ChromeRuntimeDuplexStream`, use global `E` and `kernel`/`omnium`
helpers; remove `env/dev-console` and background trusted prelude files;
add TS globals
> - Add unit and integration tests for CapTP (`background-captp`,
kernel-side CapTP, and E() end-to-end); introduce
`vitest.integration.config.ts` and `test:integration` scripts; CI gains
an "Integration Tests" job
> - Update deps (add `@endo/captp`, `@endo/eventual-send`), tsconfigs,
build constants, and minor coverage thresholds
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cbf22fd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/controller-architecture branch from d014ffd to 1ab49c9 Compare January 20, 2026 22:09
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 9ab3158 to 84ed1b2 Compare January 20, 2026 22:19
github-merge-queue bot pushed a commit that referenced this pull request Jan 22, 2026
Introduces "controllers" to Omnium. A controller is an object (class)
responsible for a slice of application state. We inherit this
nomenclature from MetaMask.

In detail:
- Add modular controller architecture with POLA attenuation via
`makeFacet()`
- `makeFacet()` is tentative, and may be removed later in the stack in
favor of just public and private methods.
- Add `ControllerStorage` abstraction with `immer`-based state
management and debounced persistence
- Add platform storage adapters and Chrome-specific implementation
- Add `Controller` abstract base class
- Add `CapletController` for managing installed caplets

**Part 2 of 4 in PR stack**
Depends on: #751
Followed by: #753 



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces a modular controller layer and integrates it into the
extension background, enabling managed, persisted state and caplet
lifecycle operations.
> 
> - **Controller framework:** Adds abstract `Controller`,
`ControllerStorage` (immer-based with debounced, per-key persistence),
and a Chrome storage adapter
> - **Caplet management:** New `CapletController` with
`install/uninstall/list/get`, manifest validation, and kernel subcluster
launch/terminate via `initializeControllers`
> - **Background integration:** Refactors globals to `omnium` (`E`,
`getKernel`, `ping`) and exposes `omnium.caplet` methods; adds error
propagation to offscreen stream
> - **Types/utilities:** Exports `Promisified` type; caplet types/guards
with superstruct and semver
> - **Config/tests:** Adds deps (exo, superstruct, immer, semver),
grants `storage` permission in manifest, and comprehensive unit/e2e
tests
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0e330b6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Base automatically changed from rekm/controller-architecture to main January 22, 2026 19:08
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from 84ed1b2 to 675ff3e Compare January 22, 2026 20:57
@github-actions
Copy link
Contributor

github-actions bot commented Jan 22, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 88.1%
⬇️ -0.12%
5713 / 6484
🔵 Statements 87.99%
⬇️ -0.11%
5805 / 6597
🔵 Functions 87.1%
⬇️ -0.09%
1486 / 1706
🔵 Branches 84.47%
⬇️ -0.05%
2063 / 2442
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-browser-runtime/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-browser-runtime/src/kernel-worker/captp/kernel-facade.ts 90%
⬇️ -10.00%
100%
🟰 ±0%
87.5%
⬇️ -12.50%
90%
⬇️ -10.00%
43
packages/kernel-browser-runtime/src/rpc-handlers/launch-subcluster.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-test/src/utils.ts 86.95%
🟰 ±0%
70.58%
🟰 ±0%
94.44%
🟰 ±0%
86.66%
🟰 ±0%
43, 112, 117, 158-173
packages/ocap-kernel/src/Kernel.ts 94.66%
🟰 ±0%
87.5%
🟰 ±0%
92.3%
🟰 ±0%
94.66%
🟰 ±0%
111, 217-220, 402, 472
packages/ocap-kernel/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/ocap-kernel/src/vats/SubclusterManager.ts 98.5%
⬆️ +0.02%
94.44%
🟰 ±0%
100%
🟰 ±0%
98.5%
⬆️ +0.02%
221-223
packages/omnium-gatherum/src/background.ts 0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
0%
🟰 ±0%
23-244
packages/omnium-gatherum/src/global.d.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/omnium-gatherum/src/controllers/index.ts 0%
🟰 ±0%
100%
⬆️ +100.00%
0%
🟰 ±0%
0%
🟰 ±0%
66-99
packages/omnium-gatherum/src/controllers/caplet/caplet-controller.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/omnium-gatherum/src/controllers/caplet/types.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
Generated in workflow #3324 for commit bc6e395 by the Vitest Coverage Report Action

@rekmarks rekmarks marked this pull request as ready for review January 23, 2026 03:43
@rekmarks rekmarks marked this pull request as draft January 23, 2026 03:44
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Comment on lines +41 to +44
// Return wrapped kref for future CapTP marshalling to presence
// TODO: Enable custom CapTP marshalling tables to convert this to a presence
return { kref: krefString };
},
Copy link
Member Author

@rekmarks rekmarks Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Bugbot so astutely points out, this doesn't work, and won't work until #754.

rekmarks and others added 7 commits January 23, 2026 13:48
Implements Phase 1a of the caplet system, establishing the foundational
architecture for caplet vats with a working echo-caplet example. This
validates the caplet vat contract and installation lifecycle before
tackling service injection complexity.

Changes:
- Add comprehensive caplet vat contract documentation
- Create echo-caplet.js demonstrating buildRootObject pattern
- Add bundle build script using @endo/bundle-source
- Implement caplet integration tests (8 new tests, all passing)
- Create test fixtures for caplet manifests
- Refactor makeMockStorageAdapter to support shared storage
- Add plan in .claude/plans for follow-up work

Key achievements:
- Caplet vat contract fully documented with examples
- Echo-caplet bundles successfully (696KB)
- Install/uninstall lifecycle tested and working
- Service lookup by name validated
- State persistence across controller restarts verified
- 100% code coverage for CapletController maintained

Deferred to future work (Phase 1b):
- Kref capture mechanism
- Service parameter injection
- Consumer caplet implementation
- Two-caplet communication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements Phase 1b functionality to store caplet root kernel references (krefs) and expose them via omnium.caplet.getCapletRoot().

This enables: omnium.caplet.install(manifest), omnium.caplet.getCapletRoot(capletId), and E(presence).method() for calling vat methods from background console.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add explicit type annotation for kernelP and use spread operator for optional rootKref field.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove KrefWrapper type from kernel-browser-runtime types
- Make rootKref a required string field in LaunchResult (not optional)
- Make rootKref required in InstalledCaplet and omnium LaunchResult
- Add assertions in kernel-facade for capData, subclusterId, and rootKref
- Remove isKrefWrapper function (inline check kept in makeKrefTables)
- Update tests to use simplified types and improved mocks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests for validation errors in kernel-facade launchSubcluster:
- Throws when kernel returns no capData
- Throws when capData body has no subclusterId
- Throws when capData slots is empty (no root kref)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add omnium.manifests.echo so users can install caplets from the console:
  await omnium.caplet.install(omnium.manifests.echo)

Changes:
- Create src/manifests.ts with echo caplet manifest using chrome.runtime.getURL
- Add echo-caplet.bundle to vite static copy targets
- Expose manifests in background.ts via omnium.manifests
- Update global.d.ts with manifests type and missing getCapletRoot

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add omnium.loadCaplet(id) to dynamically fetch caplet manifest and bundle
- Fix vatPowers.logger missing in browser vats (iframe.ts)
- Fix SubclusterLaunchResult to return bootstrapRootKref directly
  instead of trying to extract it from bootstrap() return slots

The bootstrapRootKref is the kref of the vat root object, which is
already known when the vat launches. Previously we incorrectly tried
to get it from the slots of the bootstrap() method return value.

Next step: Wire up CapTP marshalling so E(root).echo() works with
the caplet root presence.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
rekmarks and others added 12 commits January 23, 2026 13:48
Use nullable() instead of optional() for bootstrapResult field, and
define a JSON-compatible LaunchSubclusterRpcResult type that uses null
instead of undefined for JSON serialization.

Also update tests to match the new behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add comprehensive unit test coverage for the getCapletRoot method:
- Test successful retrieval of caplet root and getVatRoot call verification
- Test error case when caplet not found
- Test error case when caplet has no root object (empty rootKref)

Co-Authored-By: Claude <noreply@anthropic.com>
Move caplet source, bundle, and manifest to a single directory structure
(src/caplets/echo/) to keep related files together. Add bundleSpec to the
manifest so it's complete. Simplify loadCaplet() and test fixtures by having
them resolve bundleSpec at runtime.

Changes:
- Move src/vats/echo-caplet.js → src/caplets/echo/echo-caplet.js
- Move src/caplets/echo.manifest.json → src/caplets/echo/manifest.json
- Add bundleSpec: "echo-caplet.bundle" to manifest
- Update build:vats script to build:caplets targeting src/caplets/echo
- Simplify vite.config.ts static copy targets (no more rename logic)
- Update loadCaplet() to fetch manifest from caplet subdirectory
- Update test fixtures to import real manifest and override bundleSpec
- Fix global.d.ts to correctly type omnium.caplet.load
- Add caplets/*.js to eslint exemptions (like vats/*.js)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove vatPowers logger parameter from echo-caplet since console.log
is sufficient for logging. This also reverts the vatPowers provision
in iframe.ts that was only added to support this.

Co-Authored-By: Claude <noreply@anthropic.com>
@rekmarks rekmarks force-pushed the rekm/caplet-implementation branch from e0d8b07 to bc6e395 Compare January 23, 2026 21:49
@rekmarks rekmarks marked this pull request as ready for review January 23, 2026 21:57
Copy link
Contributor

@FUDCo FUDCo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprisingly straightforward.

@rekmarks rekmarks added this pull request to the merge queue Jan 23, 2026
Merged via the queue into main with commit 096702b Jan 23, 2026
30 checks passed
@rekmarks rekmarks deleted the rekm/caplet-implementation branch January 23, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants